﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SelfMadeMillionaire.BLL.Services;
using SelfMadeMillionaire.DAL.Repositories;
using SelfMadeMillionaire.DAL;
using HtmlAgilityPack;
using SelfMadeMillionaire.Core.Entities;
using System.Threading;
using System.Data.SqlClient;

namespace YahooFinanceScraper
{
    class Program
    {
        static void Main(string[] args)
        {
            string url = "http://finance.yahoo.com/q/op?s={0}+Options";
            string symbolUrl = "http://finance.yahoo.com/q?s={0}";
            DateTime dateAdded = DateTime.Now;
            DataContext context = new DataContext();

            UserSymbolService serviceSymbol = new UserSymbolService(new UserSymbolRepository(context));
            SymbolOptionsHistoryService serviceHistory = new SymbolOptionsHistoryService(new SymbolOptionsHistoryRepository(context));

            List<string> symbols = serviceSymbol.GetAll().Select(x => x.SymbolCode.ToUpper()).Distinct().ToList();
            foreach (string item in symbols)
            {
                string logMessage = "Parsing symbol " + item + "...";
                Console.WriteLine(logMessage);
                Utils.WriteLog(logMessage);

                var symbolInfo = CompanyInfo.ParseCompany(item);
                if(symbolInfo == null)
                {
                    logMessage = "Symbol not found";
                    Console.WriteLine(logMessage);
                    Utils.WriteLog(logMessage);
                    continue;
                }

                HtmlWeb web = new HtmlWeb();
                HtmlDocument doc = web.Load(string.Format(url, item));

                try
                {
                    /*var infoNodes = doc.DocumentNode.Descendants("form")
                                        .Where(x => x.Attributes.Contains("class") &&
                                                    x.Attributes["class"].Value.Contains("SelectBox") &&
                                                    x.Attributes["class"].Value.Contains("Grid-U")).ToList();*/

                    var infoNodes = doc.DocumentNode.SelectNodes("//select[@class='Start-0']");
                    
                    if (infoNodes == null)
                    {
                        logMessage = "Dates not found!";
                        Console.WriteLine(logMessage);
                        Utils.WriteLog(logMessage);

                        continue;
                    }

                    infoNodes = infoNodes[0].SelectNodes("option");

                    foreach (var nodeDate in infoNodes)
                    {
                        DateTime? date = Utils.ParseFullDate(nodeDate.NextSibling.InnerText);
                        if (date == null)
                        {
                            logMessage = "Wrong date format: " + nodeDate.NextSibling.InnerText;
                            Console.WriteLine(logMessage);
                            Utils.WriteLog(logMessage);

                            continue;
                        }

                        string optionUrl = "http://finance.yahoo.com" + nodeDate.Attributes["data-selectbox-link"].Value;

                        logMessage = string.Format("Scarping date (url: {1}): {0}", nodeDate.NextSibling.InnerText, optionUrl);
                        Console.WriteLine(logMessage);
                        Utils.WriteLog(logMessage);

                        doc = web.Load(optionUrl);
                        Thread.Sleep(3000);

                        var priceNode = doc.DocumentNode.SelectNodes(string.Format("//span[@data-sq='{0}:value']", item));
                        if (priceNode == null)
                        {
                            logMessage = "Price not found!";
                            Console.WriteLine(logMessage);
                            Utils.WriteLog(logMessage);

                            continue;
                        }
                        double priceSymbol = double.Parse(priceNode[0].InnerText);
                        
                        var callsNodes = doc.DocumentNode.SelectNodes("//div[@id='optionsCallsTable']/div[@class='follow-quote-area']/div/table");
                        if (callsNodes == null)
                        {
                            logMessage = "Calls not found!";
                            Console.WriteLine(logMessage);
                            Utils.WriteLog(logMessage);
                        }
                        else
                        {
                            WriteHistory(context, item, dateAdded, date.Value, true, priceSymbol, callsNodes[0], symbolInfo);
                        }

                        var putsNodes = doc.DocumentNode.SelectNodes("//div[@id='optionsPutsTable']/div[@class='follow-quote-area']/div/table");
                        if (callsNodes == null)
                        {
                            logMessage = "Puts not found!";
                            Console.WriteLine(logMessage);
                            Utils.WriteLog(logMessage);
                        }
                        else
                        {
                            WriteHistory(context, item, dateAdded, date.Value, false, priceSymbol, putsNodes[0], symbolInfo);
                        }

                    }
                }
                catch(Exception e)
                {
                    Console.WriteLine("Exception: " + e.Message);
                    Utils.WriteLog("Exception: " + e.Message);
                }

            }

            //Console.ReadKey();
            
        }

        public static void WriteHistory(DataContext context, string symbol, DateTime addDate, DateTime optionDate, bool isCall, double priceSymbol, HtmlNode node, CompanyInfo symbolInfo)
        {
            HtmlNodeCollection rows = node.SelectNodes("tbody/tr");
            if (rows == null) return;

            for (int i = 0; i < rows.Count; ++i)
            {
                HtmlNode row = rows[i];
                HtmlNodeCollection cellsData = row.SelectNodes("td");
                
                SymbolOptionsHistory dbItem = new SymbolOptionsHistory();
                dbItem.AddDateTime = addDate;
                dbItem.Symbol = symbol;
                dbItem.OptionDate = optionDate;
                dbItem.IsCalls = isCall;
                dbItem.SymbolPrice = priceSymbol;

                dbItem.Strike = double.Parse(Utils.PrepareInnerText(cellsData[0].InnerText));
                dbItem.ContractName = Utils.PrepareInnerText(cellsData[1].InnerText);
                dbItem.Last = double.Parse(Utils.PrepareInnerText(cellsData[2].InnerText));
                dbItem.Bid = double.Parse(Utils.PrepareInnerText(cellsData[3].InnerText));
                dbItem.Ask = double.Parse(Utils.PrepareInnerText(cellsData[4].InnerText));
                dbItem.Change = double.Parse(Utils.PrepareInnerText(cellsData[5].InnerText));
                dbItem.PercentChange = double.Parse(Utils.PrepareInnerText(cellsData[6].InnerText.Replace("%", "")));
                dbItem.Volume = int.Parse(Utils.PrepareInnerText(cellsData[7].InnerText));
                dbItem.OpenInterest = int.Parse(Utils.PrepareInnerText(cellsData[8].InnerText));
                dbItem.ImpliedVolatility = double.Parse(Utils.PrepareInnerText(cellsData[9].InnerText.Replace("%", "")));

                CalcProbability(dbItem);

                List<SqlParameter> sqlParams = new List<SqlParameter>();
                sqlParams.Add(new SqlParameter("@Symbol", dbItem.Symbol));
                sqlParams.Add(new SqlParameter("@OptionDate", dbItem.OptionDate));
                sqlParams.Add(new SqlParameter("@IsCalls", dbItem.IsCalls));
                sqlParams.Add(new SqlParameter("@AddDateTime", dbItem.AddDateTime));
                sqlParams.Add(new SqlParameter("@Strike", dbItem.Strike));
                sqlParams.Add(new SqlParameter("@ContractName", dbItem.ContractName));
                sqlParams.Add(new SqlParameter("@Last", dbItem.Last));
                sqlParams.Add(new SqlParameter("@Bid", dbItem.Bid));
                sqlParams.Add(new SqlParameter("@Ask", dbItem.Ask));
                sqlParams.Add(new SqlParameter("@Change", dbItem.Change));
                sqlParams.Add(new SqlParameter("@PercentChange", dbItem.PercentChange));
                sqlParams.Add(new SqlParameter("@Volume", dbItem.Volume));
                sqlParams.Add(new SqlParameter("@OpenInterest", dbItem.OpenInterest));
                sqlParams.Add(new SqlParameter("@ImpliedVolatility", dbItem.ImpliedVolatility));
                sqlParams.Add(new SqlParameter("@SymbolPrice", dbItem.SymbolPrice));
                sqlParams.Add(new SqlParameter("@Probability", dbItem.Probability));
                sqlParams.Add(new SqlParameter("@BidImmediateReturn", (isCall ? double.Parse(symbolInfo.Bid) : dbItem.Bid)/(priceSymbol)));
                sqlParams.Add(new SqlParameter("@AskImmediateReturn", (isCall ? double.Parse(symbolInfo.Ask) : dbItem.Strike)/(priceSymbol)));
                sqlParams.Add(new SqlParameter("@LastImmediateReturn", dbItem.Last/(priceSymbol*100)));
                int daysInTrade = (optionDate - addDate).Days;
                daysInTrade = daysInTrade == 0 ? 1 : daysInTrade;
                sqlParams.Add(new SqlParameter("@DaysInTrade", daysInTrade));
                
                context.Database.ExecuteSqlCommand(@"spAddSymbolOptionsHistory @Symbol = @Symbol, @OptionDate = @OptionDate, @IsCalls = @IsCalls, 
                    @AddDateTime = @AddDateTime, @Strike = @Strike, @ContractName = @ContractName, @Last = @Last, @Bid = @Bid, @Ask = @Ask, 
                    @Change = @Change, @PercentChange = @PercentChange, @Volume = @Volume, @OpenInterest = @OpenInterest, 
                    @ImpliedVolatility = @ImpliedVolatility, @SymbolPrice = @SymbolPrice, @Probability = @Probability,
                    @BidImmediateReturn = @BidImmediateReturn, @AskImmediateReturn = @AskImmediateReturn, @LastImmediateReturn = @LastImmediateReturn,
                    @DaysInTrade = @DaysInTrade", sqlParams.ToArray());

                //service.Add(dbItem);
            }
        }

        public static void CalcProbability(SymbolOptionsHistory item)
        {
            double p = item.SymbolPrice;
            double q = item.Strike;

            double t = Math.Abs((DateTime.Today - item.OptionDate).TotalDays) / 365;
            double v = item.ImpliedVolatility / 100;
            double vt = v * Math.Sqrt(t);
            double lnpq = Math.Log(q/p);
            double d1 = lnpq / vt;

            double y = Math.Floor(1 / (1 + 0.2316419 * Math.Abs(d1)) * 100000) / 100000;
            double z = Math.Floor(0.3989423 * Math.Exp(-((d1*d1) / 2)) * 100000) / 100000;
            double y5 = 1.330274 * Math.Pow(y, 5);
            double y4 = 1.821256 * Math.Pow(y, 4);
            double y3 = 1.781478 * Math.Pow(y,3);
            double y2 = 0.356538 * Math.Pow(y, 2);
            double y1 = 0.3193815 * y;
            double x = 1 - z * (y5 - y4 + y3 - y2 + y1);
            x = Math.Floor(x * 100000) / 100000;

            if (d1 < 0)
                x = 1 - x;
            
            double pabove = Math.Floor(x * 1000) / 10; 
            double pbelow = Math.Floor((1 - x) * 1000) / 10;

            if (item.IsCalls)
                item.Probability = pbelow;
            else
                item.Probability = pabove;
        }
    }
}
